package puzzle.core;

import java.io.IOException;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.Sound;
import org.newdawn.slick.particles.ConfigurableEmitter;
import org.newdawn.slick.particles.ParticleIO;
import org.newdawn.slick.particles.ParticleSystem;
import org.newdawn.slick.util.Log;
import puzzle.core.Direction;
import puzzle.core.Directional;
import puzzle.core.GameContext;
import puzzle.core.LevelMap;
import puzzle.core.Player;
import puzzle.core.Tile;
import puzzle.core.TileType;
import puzzle.core.tiles.BoostTile;
import puzzle.core.tiles.PortTile;

public class Player implements Directional {
  
  /** Whether to render ball bounds. */
  protected boolean renderBounding = false;
  
  /** Whether the ball is moving. */
  private boolean moving = false;
  
  /** Which direction the ball is currently moving in. */
  private Direction direction = Direction.NONE;
  
  /** The speed the ball moves at. */
  private float speed = 0.2f;
  
  /** Where to move along the X-axis. */
  private int moveX = 0;
  
  /** Where to move along the Y-axis. */
  private int moveY = 0;
  
  /** The GameContext this player is in. */
  private GameContext ctx = null;
  
  /** The x position of the Player. */
  private float x = 255f;
  
  /** The y position of the Player. */
  private float y = 25f;
  
  /** The image of the ball. */
  private Image ballImg = null;
  
  ParticleSystem system = null;
  ConfigurableEmitter emitter = null;
  
  public Player(Image img, String pref, GameContext ctx) throws SlickException {
    this.ctx = ctx;
    if (this.ctx == null)
      throw new NullPointerException();
   
    try {
      system = ParticleIO.loadConfiguredSystem(pref);
      if (system.getEmitterCount()>=1
                && system.getEmitter(0) instanceof ConfigurableEmitter) {
        emitter = (ConfigurableEmitter)(system.getEmitter(0));
      }
      
      if (emitter==null) {
        throw new IOException("Can't find configurableEmitter");
      }
      
      system.removeEmitter(emitter);
    } catch (IOException e) {
      Log.error("Failed to load particle system.", e);
    }
      
    this.ballImg = img;
  }
  
  public GameContext getContext() {
    return ctx;
  }
  
  public float getX() {
    return x;
  }
  
  public float getY() {
    return y;
  }
  
  public void setX(float x) {
    this.x = x;
  }
  
  public void setY(float y) {
    this.y = y;
  }
  
  public void setLocation(float x, float y) {
    this.x = x;
    this.y = y;
  }
  
  public void placeAtTile(int x, int y) {
    float fx = x * Tile.WIDTH + (Tile.WIDTH/2.0f - ballImg.getWidth()/2.0f);
    float fy = y * Tile.HEIGHT + (Tile.HEIGHT/2.0f - ballImg.getHeight()/2.0f);
    setLocation(fx, fy);
  }
        
  /** 
   * Checks to see whether the specified move is valid. 
   *
   * @param e The tile we are about to collide with
   * @param x The x position we are to move to
   * @param y The y position we are to move to
   */
  protected boolean validMove(int xIndex, int yIndex) {
    LevelMap map = getContext().getCurrentLevel();
    Tile t = map.getTile(xIndex, yIndex);
    
    TileType id = t.getTileType();
    
    if (id==TileType.GROUND) {
      //do nothing
      return true;
    }
    else if (id==TileType.WALL) {
      //play sound
      
      //collide
      return false;
    }
    else if (id==TileType.END) {
      Sound sfx = getContext().getEndSound();
      if (sfx!=null) {
        if (sfx.playing())
          sfx.stop();
        sfx.play();
      }
      if (system!=null)
        system.removeAllEmitters();
      //end level
      getContext().endLevel();
      return true;
    }
    else if (id==TileType.BOOST){
      BoostTile boost = (BoostTile)t;
        
      boolean valid = (this.getDirection() == boost.getDirection());
      
      if (valid) {
        //play sound
      }
      
      return valid;
    }
    else if (id==TileType.PORT) {
      PortTile port = (PortTile)t;
            
      if (system!=null) {
        system.addEmitter(emitter);
        float fx = xIndex * Tile.WIDTH + (Tile.WIDTH/2.0f);
        float fy = yIndex * Tile.HEIGHT + (Tile.HEIGHT/2.0f);
        system.setPosition(fx,fy);
        emitter.replay();
        
        
      }
      Sound psfx = getContext().getPortSound();
      if (psfx!=null) {
            if (psfx.playing())
                psfx.stop();
            psfx.play();
      }
        
      int lx = port.getLinkX();
      int ly = port.getLinkY();
      
      Direction ldir = port.getLinkDirection();
      
      placeAtTile(lx, ly);
      
      setDirection(ldir);
      
      return true;
    }
    return true;
  }
  
  /** Sets the ball to move in the specified Direction. */
  public void setDirection(Direction d) {
    moving = (d!=Direction.NONE);
    direction = (d!=null) ? d : Direction.NONE;
    
    switch (getDirection()) {
      case UP:
        moveY = -1;
        moveX = 0;
        break;
      case DOWN:
        moveY = 1;
        moveX = 0;
        break;
      case LEFT:
        moveX = -1;
        moveY = 0;
        break;
      case RIGHT:
        moveX = 1;
        moveY = 0;
        break;
      default:
        moveX = 0;
        moveY = 0;
        break;
    }
  }
  
  public Direction getDirection() {
    return direction;
  }
  
  public boolean isMoving() {
    return moving;
  }
  
  public void update(GameContainer c, int delta) {
    if (system!=null)
      system.update(delta);
    
    //ensures that the ball is moving AND
    //the move is valid before translating position
    if (moving&&direction!=Direction.NONE) {
      float oldX = x;
      float oldY = y;
      
      //the move we wish to make
      x += moveX * delta * speed;
      y += moveY * delta * speed;
      
      //size of the ball image
      int ballWidth = ballImg.getWidth();
      int ballHeight = ballImg.getHeight();
      
      //move player to other side of screen if needed
      if (x > LevelMap.PIXEL_WIDTH) {
        x = -ballWidth;
      }
      else if (x < -ballWidth) {
        x = LevelMap.PIXEL_WIDTH;
      }
      if (y > LevelMap.PIXEL_HEIGHT) {
        y = -ballHeight;
      }
      else if (y < -ballHeight) {
        y = LevelMap.PIXEL_HEIGHT;
      }
      
      float ax = x + moveX*(Tile.WIDTH/2.0f - ballWidth/2.0f);
      float ay = y + moveY*(Tile.HEIGHT/2.0f - ballHeight/2.0f);
      
      if (direction==Direction.RIGHT) {
        ax += ballWidth;
      }
      else if (direction==Direction.DOWN) {
        ay += ballHeight;
      }
      
      //get expected index
      int xIndex = (int)(ax / Tile.WIDTH);
      int yIndex = (int)(ay / Tile.HEIGHT);
            
      if (xIndex<0)
        xIndex = LevelMap.WIDTH-1;
      else if (xIndex>LevelMap.WIDTH-1)
        xIndex = 0;
        
      if (yIndex<0)
        yIndex = LevelMap.HEIGHT-1;
      else if (yIndex>LevelMap.HEIGHT-1)
        yIndex = 0;
      
      //not a valid move
      if (!validMove(xIndex, yIndex)) {        
        moving = false;
        direction = Direction.NONE;
        
        x = oldX;
        y = oldY;
        
        placeAtTile((int)(x / Tile.WIDTH), (int)(y / Tile.HEIGHT));
      }
      //end of updating...
    } //emds if-moving check
  }
  
  public void render(Graphics g) {
    Color old = g.getColor();
    
    if (system!=null)
      system.render();
    
    g.drawImage(ballImg, (int)x, (int)y);
    
    if (renderBounding) {
      g.setColor(Color.red);
      g.drawRect((int)x, (int)y, ballImg.getWidth(), ballImg.getHeight());
    }
    
    g.setColor(old);
  }
  
  public void setShowBounds(boolean b) {
    renderBounding = b;
  }
  
  public boolean isShowBounds() {
    return renderBounding;
  }
}

/*
      //the old positions
      float oldX = x;
      float oldY = y;
      
      //the new position we are expected to move to
      x += moveX * delta * speed;
      y += moveY * delta * speed;
      
      //gets the current map
      LevelMap map = getContext().getCurrentLevel();
      
      //move player to other side of screen if needed
      if (x > LevelMap.PIXEL_WIDTH) {
        x = -ballImg.getWidth();
      }
      else if (x < -ballImg.getWidth()) {
        x = LevelMap.PIXEL_WIDTH;
      }
      if (y > LevelMap.PIXEL_HEIGHT) {
        y = -ballImg.getHeight();
      }
      else if (y < -ballImg.getHeight()) {
        y = LevelMap.PIXEL_HEIGHT;
      }
      
      //get expected index
      int xIndex = (int) (x / Tile.WIDTH);
      int yIndex = (int) (y / Tile.HEIGHT);
      // + (Tile.HEIGHT*moveY))
      
      switch (direction) {
        case LEFT:
          break;
        case RIGHT:
          xIndex++;
          break;
        case UP:
          break;
        case DOWN:
          yIndex++;
      }
      
      if (xIndex<0) 
        xIndex = LevelMap.WIDTH-1;
      else if (xIndex>LevelMap.WIDTH-1)
        xIndex = 0;
      if (yIndex<0)
        yIndex = LevelMap.HEIGHT-1;
      else if (yIndex>LevelMap.HEIGHT-1)
        yIndex = 0;
      
      //gets the tile ...
      Tile t = map.getTile(xIndex, yIndex);
            
      //not a valid move
      if (!validMove(t, x, y)) {
        moving = false;
        direction = Direction.NONE;
        
        x = oldX;
        y = oldY;
        
        int tilex = xIndex*Tile.WIDTH;
        int tiley = yIndex*Tile.HEIGHT;
        
        x += Math.max(0, tilex-(Tile.WIDTH-1+x));
        y += Math.max(0, tiley-(Tile.HEIGHT-1+y));
      }
      //end of updating...
    }
*/